30分钟摸透iOS中谓词NSPredicate的来龙去脉
一、引言
在现代汉语的解释中,谓词是用来描述或判断客体性质、特征或者客体之间关系的词项。通俗的说,它是描述事物属性的。在iOS开发Cocoa框架中,有提供NSPredicate类,这个类通常也被成为谓词类,其主要的作用是在Cocoa中帮助查询和检索,但是需要注意,实质上谓词并不是提供查询和检索的支持,它是一种描述查询检索条件的方式,就像更加标准通用的正则表达式一样。
NSPredicate提供的谓词可以分为两类:比较谓词和复合谓词。
- 比较谓词:比较谓词通过使用比较运算符来描述所符合条件的属性状态。
- 复合谓词:复合谓词用来组合多个比较谓词的结果,取交集,并集或补集。
对于比较谓词,可以描述精准的比较也可以通过范围或者包含等进行模糊比较。需要注意,任何Cocoa类对象都可以支持谓词,但是此类需要实现键值编码(key-value-coding)协议。
二、NSPredicate类的应用解析
NSPredicate提供创建谓词对象和解析谓词对象的方法,它也是Cocoa中有关谓词的类中的基类。我们在日常开发中,NSPredicate类的应用频率也最高。
创建谓词对象有3种方式,分别是通过格式化字符串创建谓词,直接通过代码创建谓词,通过模板创建谓词。NSPredicate提供了如下函数来进行初始化:
1 2 3 4
| + (NSPredicate *)predicateWithFormat:(NSString *)predicateFormat argumentArray:(nullable NSArray *)arguments; + (NSPredicate *)predicateWithFormat:(NSString *)predicateFormat, ...; + (NSPredicate *)predicateWithFormat:(NSString *)predicateFormat arguments:(va_list)argList;
|
使用格式化字符串进行谓词的初始化十分灵活,但是需要注意,其谓词字符串的语法和正则表达式并不一样,后面会有具体的介绍,下面是一个谓词检索示例:
1 2 3 4 5 6 7
| NSPredicate * predicate = [NSPredicate predicateWithFormat:@"length = 5"];
NSArray * test = @[@"sda",@"321",@"sf12",@"dsdwq1",@"swfas"]; NSArray * result = [test filteredArrayUsingPredicate:predicate];
NSLog(@"%@",result);
|
其实,你也可以像使用NSLog函数一样来进行格式化字符串的构造,可以使用%@,%d等等格式化字符来在运行时替换为变量的实际值。同时也需要注意,这种格式化字符串创建的谓词语句并不会进行语法检查,错误的语法会产生运行时错误,要格外小心。有一个小细节需要注意,在进行格式化时,如果使用的是变量则不需要添加引号,解析器会帮助你添加,如果使用到常量,则要用转义字符进行转义,例如:
1
| NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name = %@ && age = \"25\"",name];
|
对于属性名,如果也需要进行格式化,需要注意不能使用%@符号,这个符号在解析时会被解析器自动添加上引号,可以使用%K,示例如下:
1 2 3 4 5 6
| NSString * key = @"length"; NSPredicate * predicate = [NSPredicate predicateWithFormat:@"%K = 5",key]; NSArray * test = @[@"sda",@"321",@"sf12",@"dsdwq1",@"swfas"]; NSArray * result = [test filteredArrayUsingPredicate:predicate];
NSLog(@"%@",result);
|
通过模板来创建谓词对象也是一种十分常用的方式,和格式化字符串不同的是,谓词模板中只有键名,没有键值,键值需要在字典中进行提供,例如:
1 2 3 4 5 6
| NSPredicate * predicate = [NSPredicate predicateWithFormat:@"length = $LENGTH"]; predicate = [predicate predicateWithSubstitutionVariables:@{@"LENGTH":@5}]; NSArray * test = @[@"sda",@"321",@"sf12",@"dsdwq1",@"swfas"]; NSArray * result = [test filteredArrayUsingPredicate:predicate];
NSLog(@"%@",result);
|
NSPredicate中其他属性与方法解析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
+ (NSPredicate *)predicateWithValue:(BOOL)value;
+ (NSPredicate*)predicateWithBlock:(BOOL (^)(id _Nullable evaluatedObject, NSDictionary<NSString *, id> * _Nullable bindings))block;
@property (readonly, copy) NSString *predicateFormat;
- (instancetype)predicateWithSubstitutionVariables:(NSDictionary<NSString *, id> *)variables;
- (BOOL)evaluateWithObject:(nullable id)object;
- (BOOL)evaluateWithObject:(nullable id)object substitutionVariables:(nullable NSDictionary<NSString *, id> *)bindings;
|
三、通过代码来创建谓词对象
前面我们说有3种创建谓词对象的方式,有两种我们已经有介绍,通过代码直接创建谓词对象是最复杂的一种。通过代码来创建谓词对象十分类似通过代码来创建Autolayout约束。通过前面我们的介绍,谓词实际是用表达式来验证对象,用代码来创建谓词实际就是用代码来创建表达式。
1.先来看NSComparisonPredicate类
这个类是NSPredicate的子类,其用来创建比较类型的谓词。例如使用下面的代码来改写上面的例子:
1 2 3 4 5 6 7 8 9 10
| NSExpression * left = [NSExpression expressionForKeyPath:@"length"];
NSExpression * right = [NSExpression expressionForConstantValue:[NSNumber numberWithInt:5]];
NSComparisonPredicate * pre = [NSComparisonPredicate predicateWithLeftExpression:left rightExpression:right modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:NSCaseInsensitivePredicateOption]; NSArray * test = @[@"sda",@"321",@"sf12",@"dsdwq1",@"swfas"]; NSArray * result = [test filteredArrayUsingPredicate:pre];
NSLog(@"%@",result);
|
NSComparisonPredicateModifier用来进行条件的修饰设置,枚举如下:
1 2 3 4 5
| typedef NS_ENUM(NSUInteger, NSComparisonPredicateModifier) { NSDirectPredicateModifier = 0, NSAllPredicateModifier, NSAnyPredicateModifier };
|
关于NSAllPredicateModifier和NSAnyPredicateModifier,这两个枚举专门用于数组或集合类型对象的验证,ALL会验证其中所有元素,全部通过后数组或集合才算验证通过,ANY则只要有一个元素验证通过,数组或集合就算验证通过,例如:
1 2 3 4 5
| NSPredicate * pre = [NSPredicate predicateWithFormat:@"ALL length = 5"]; NSArray * test = @[@[@"aaa",@"aa"],@[@"bbbb",@"bbbbb"],@[@"ccccc",@"ccccc"]]; NSArray * result = [test filteredArrayUsingPredicate:pre]; NSLog(@"%@",result);
|
NSPredicateOperatorType枚举用来设置运算符类型,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| typedef NS_ENUM(NSUInteger, NSPredicateOperatorType) { NSLessThanPredicateOperatorType = 0, NSLessThanOrEqualToPredicateOperatorType, NSGreaterThanPredicateOperatorType, NSGreaterThanOrEqualToPredicateOperatorType, NSEqualToPredicateOperatorType, NSNotEqualToPredicateOperatorType, NSMatchesPredicateOperatorType, NSLikePredicateOperatorType, NSBeginsWithPredicateOperatorType, NSEndsWithPredicateOperatorType, NSInPredicateOperatorType, NSCustomSelectorPredicateOperatorType, NSContainsPredicateOperatorType, NSBetweenPredicateOperatorType };
|
NSComparisonPredicateOptions枚举用来设置比较的方式,如下:
1 2 3 4 5 6
| typedef NS_OPTIONS(NSUInteger, NSComparisonPredicateOptions) { NSCaseInsensitivePredicateOption = 0x01, NSDiacriticInsensitivePredicateOption = 0x02, NSNormalizedPredicateOption };
|
2.NSExpression类
NSExpression类则是提供创建表达式,下面列出了其中一些方便理解的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| + (NSExpression *)expressionWithFormat:(NSString *)expressionFormat argumentArray:(NSArray *)arguments; + (NSExpression *)expressionWithFormat:(NSString *)expressionFormat, ...; + (NSExpression *)expressionWithFormat:(NSString *)expressionFormat arguments:(va_list)argList;
+ (NSExpression *)expressionForConstantValue:(nullable id)obj;
+ (NSExpression *)expressionForVariable:(NSString *)string;
+ (NSExpression *)expressionForAggregate:(NSArray<NSExpression *> *)subexpressions; + (NSExpression *)expressionForUnionSet:(NSExpression *)left with:(NSExpression *)right; + (NSExpression *)expressionForIntersectSet:(NSExpression *)left with:(NSExpression *)right; + (NSExpression *)expressionForMinusSet:(NSExpression *)left with:(NSExpression *)right; + (NSExpression *)expressionForSubquery:(NSExpression *)expression usingIteratorVariable:(NSString *)variable predicate:(NSPredicate *)predicate;
+ (NSExpression *)expressionForFunction:(NSString *)name arguments:(NSArray *)parameters;
|
3.NSCompoundPredicate类
这个类也是NSPredicate类的子类,其使用逻辑关系来组合多个谓词对象,解析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
- (instancetype)initWithType:(NSCompoundPredicateType)type subpredicates:(NSArray<NSPredicate *> *)subpredicates;
+ (NSCompoundPredicate *)andPredicateWithSubpredicates:(NSArray<NSPredicate *> *)subpredicates;
+ (NSCompoundPredicate *)orPredicateWithSubpredicates:(NSArray<NSPredicate *> *)subpredicates;
+ (NSCompoundPredicate *)notPredicateWithSubpredicate:(NSPredicate *)predicate;
|
四、谓词的几种使用场景
谓词主要用在验证对象,数组和集合的过滤。对象的验证前面有介绍,关于数据和集合的过滤函数,类别如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @interface NSArray<ObjectType> (NSPredicateSupport)
- (NSArray<ObjectType> *)filteredArrayUsingPredicate:(NSPredicate *)predicate; @end
@interface NSMutableArray<ObjectType> (NSPredicateSupport)
- (void)filterUsingPredicate:(NSPredicate *)predicate; @end
@interface NSSet<ObjectType> (NSPredicateSupport)
- (NSSet<ObjectType> *)filteredSetUsingPredicate:(NSPredicate *)predicate; @end
@interface NSMutableSet<ObjectType> (NSPredicateSupport)
- (void)filterUsingPredicate:(NSPredicate *)predicate; @end
@interface NSOrderedSet<ObjectType> (NSPredicateSupport)
- (NSOrderedSet<ObjectType> *)filteredOrderedSetUsingPredicate:(NSPredicate *)p;
@end
@interface NSMutableOrderedSet<ObjectType> (NSPredicateSupport)
- (void)filterUsingPredicate:(NSPredicate *)p;
@end
|
五、谓词的格式化语法总览
下面列出了在谓词的格式化字符串规则语法。
| 语法规则 | 意义 |
| = | 左侧等于右侧 |
| == | 左侧等于右侧,与=一致 |
| >= | 左侧大于等于右侧 |
| => | 左侧大于等于右侧 与 >=一致 |
| <= | 左侧小于等于右侧 |
| =< | 左侧小于等于右侧 与<=一致 |
| > | 左侧大于右侧 |
| < | 左侧小于右侧 |
| != | 左侧不等于右侧 |
| <> | 左侧不等于右侧 与!=一致 |
| BETWEEN | 左侧在右侧的集合中 key BETWEEN @[@1,@2] |
| TRUEPREDICATE | 总是返回YES的谓词 |
| FALSEPREDICATE | 总是返回NO的谓词 |
| AND | 逻辑与 |
| && | 逻辑与 与AND一致 |
| OR | 逻辑或 |
| || | 逻辑或 与OR一致 |
| NOT | 逻辑非 |
| ! | 逻辑非 与NOT一致 |
| BEGINWITH | 左侧以右侧字符串开头 |
| ENDWITH | 左侧以右侧字符串结尾 |
| CONTAINS | 左侧集合包含右侧元素 |
| LIKE | 左侧等于右侧 并且 *和?等通配符可以使用 |
| MATCHES | 正则匹配 |
| ANY | 对于数组集合类,验证其中任一元素 |
| SOME | 同ANY一致 |
| ALL | 对于数组集合类,验证其中所有元素 |
| NONE | 作用等同于NOT (ANY) |
| IN | 左侧在右侧集合中 |
| SELF | 被验证的对象本身 |